package compiler.parser;

import compiler.scanner.*;
import compiler.scanner.Scanner;
import compiler.syntax_tree.*;

public class ProgramParser
    extends BaseParser
{
    public ProgramParser(Scanner scanner)
    {
        super(scanner);
        scanner.addObserver(this);
    }

    public Program parse()
    {
        readToken();
        MainClass mainClass = parseMainClass();
        PeekToken();
        if (currentToken.getId() != TokenId.EOF && currentToken.getId() != TokenId.COMMENT)
        {
            handleError("Invalid statement syntax");
        }
        return new Program(mainClass);
    }

    public MainClass parseMainClass()
    {
        readSpecificToken(TokenId.CLASS);
        Identifier id1 = ParseIdentifier();
        readToken();
        readSpecificToken(TokenId.LEFT_BRACE);
        readSpecificToken(TokenId.PUBLIC);
        readSpecificToken(TokenId.STATIC);
        readSpecificToken(TokenId.VOID);
        checkMainWord();
        readToken();
        readSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        readSpecificToken(TokenId.STRING);
        readSpecificToken(TokenId.LEFT_SQUARE_BRACKET);
        readSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
        Identifier id2 = ParseIdentifier();
        readToken();
        readSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        CheckSpecificToken(TokenId.LEFT_BRACE);
        PeekToken();
        Statement statement = new Block(new StatementList());
        VarDeclarationList varDeclarationList = new VarDeclarationList();
        if (currentToken.getId() != TokenId.RIGHT_BRACE)
        {
            readToken();
            varDeclarationList = parseVarDeclarationsOpt();
            statement = parseBlock();
        }

        readSpecificToken(TokenId.RIGHT_BRACE);
        readSpecificToken(TokenId.RIGHT_BRACE);
        return new MainClass(id1, id2, varDeclarationList, statement);
    }

    private VarDeclarationList parseVarDeclarationsOpt()
    {
        VarDeclarationList varDeclarationList = new VarDeclarationList();
        while (isCurrentTokenTypeToken())
        {
            TokenId currentTokenType = currentToken.getId();
            Type varType = null;
            switch (currentTokenType)
            {
                case INT_TYPE:
                    varType = new IntegerType();
                    break;
                case BOOLEAN:
                    varType = new BooleanType();
                    break;
            }
            readToken();
            if (currentToken.getId() == TokenId.LEFT_SQUARE_BRACKET)
            {
                readToken();
                readSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
                varType = new IntArrayType();
            }
            Identifier varName = ParseIdentifier();
            readToken();
            readSpecificToken(TokenId.SEMICOLON);

            VarDeclaration varDeclaration = new VarDeclaration(varType, varName);
            varDeclarationList.addElement(varDeclaration);
        }
        return varDeclarationList;
    }

    private boolean isCurrentTokenTypeToken()
    {
        TokenId tokenId = currentToken.getId();
        return (tokenId == TokenId.INT_TYPE) || (tokenId == TokenId.BOOLEAN);
    }

    private Block parseBlock()
    {
        StatementList statementList = new StatementList();
        do
        {
            statementList.addElement(parseStatement());
        }
        while (currentToken.getId() != TokenId.RIGHT_BRACE && !WasError());
        return new Block(statementList);
    }

    private Statement parseStatement()
    {
        Statement statement = null;
        switch (currentToken.getId())
        {
            case SYSTEM:
                statement = parseSystemOutPrintln();
                break;
            case IDENTIFIER:
                statement = parseAssignment();
                break;
            case IF:
                statement = parseIf();
                break;
            case WHILE:
                statement = parseWhile();
                break;
            case LEFT_BRACE:
                readToken();
                statement = parseBlock();
                readSpecificToken(TokenId.RIGHT_BRACE);
                break;
            default:
                handleError("Unexpected statement");
                break;
        }
        return statement;
    }

    private While parseWhile()
    {
        readSpecificToken(TokenId.WHILE);
        readSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        Expression condition = parseExpression();
        readSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        Statement statement = parseStatement();
        return new While(condition, statement);
    }

    private If parseIf()
    {
        readSpecificToken(TokenId.IF);
        readSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        Expression condition = parseExpression();
        readSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        Statement ifStatement = parseStatement();
        Statement elseStatement = null;
        if (currentToken.getId() == TokenId.ELSE)
        {
            readToken();
            elseStatement = parseStatement();
        }
        return new If(condition, ifStatement, elseStatement);
    }

    private Print parseSystemOutPrintln()
    {
        readSpecificToken(TokenId.SYSTEM);
        readSpecificToken(TokenId.DOT);
        readSpecificToken(TokenId.OUT);
        readSpecificToken(TokenId.DOT);
        readSpecificToken(TokenId.PRINTLN);
        readSpecificToken(TokenId.LEFT_ROUND_BRACKET);
        Expression expression = parseExpression();
        readSpecificToken(TokenId.RIGHT_ROUND_BRACKET);
        readSpecificToken(TokenId.SEMICOLON);
        return new Print(expression);
    }

    private Statement parseAssignment()
    {
        boolean isSimpleAssignment = true;
        Expression indexExpression = null;
        Identifier id = ParseIdentifier();
        readToken();
        if (currentToken.getId() == TokenId.LEFT_SQUARE_BRACKET)
        {
            isSimpleAssignment = false;
            readToken();
            indexExpression = parseExpression();
            readSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
        }
        readSpecificToken(TokenId.ASSIGN);
        Expression expression = parseExpression();
        readSpecificToken(TokenId.SEMICOLON);
        return (isSimpleAssignment) ? new Assignment(id, expression) : new ArrayAssign(id, indexExpression, expression);
    }

    private Expression parseExpression()
    {
        ExpressionParser expressionParser = new ExpressionParser(scanner, currentToken);
        Expression expressionNode = expressionParser.parse();
        readToken();
        return expressionNode;
    }

    private void checkMainWord()
    {
        if (currentToken.getId() != TokenId.IDENTIFIER || currentToken.GetData() == null || !currentToken.GetData().equals("main"))
        {
            handleError("'main' expected");
        }
    }
}
